package ru.aslanov.schedule.server.gcalendar;

import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskAlreadyExistsException;
import com.google.gdata.data.calendar.CalendarEventFeed;
import ru.aslanov.schedule.model.*;
import ru.aslanov.schedule.server.PMF;
import ru.aslanov.schedule.shared.SyncStatus;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;

/**
 * Created by IntelliJ IDEA.
 * Created: Feb 27, 2010 5:16:57 PM
 *
 * @author Sergey Aslanov
 */
public class GCalendarSyncService  {
    private static final Logger log = Logger.getLogger(GCalendarSyncService.class.getName());

    //private static final ConcurrentMap<String, ConcurrentLinkedQueue<GoogleCalendarSyncEvent>> scheduleEventQueues
    //        = new ConcurrentHashMap<String, ConcurrentLinkedQueue<GoogleCalendarSyncEvent>>();
    //private static final ConcurrentMap<String, ConcurrentLinkedQueue<String>> scheduleCalendarSyncQueues
    //        = new ConcurrentHashMap<String, ConcurrentLinkedQueue<String>>();


    public void loadFromGCalendar(String scheduleKey) throws Exception {
        final PersistenceManager pm = PMF.getThreadLocalPersistenceManager();
        final Transaction tran = pm.currentTransaction();

        try {
            final Schedule schedule = pm.getObjectById(Schedule.class, scheduleKey);
            final List<GCalendar> calendars = schedule.getCalendars();

            GCalParser gCalParser = new GCalParser(schedule);
            final Date now = new Date();

            List<Group> allGroups = new ArrayList<Group>();

            for (GCalendar calendar : calendars) {
                log.info("Downloading calendar events for " + calendar.getCalendarId() + " (" + calendar.getName() + ")");
                //final Document document = GCalendarConnectService.loadEvents(calendar.getCalendarId());

                GCalendarConnectService gCalendarConnectService = new GCalendarConnectService(scheduleKey);
                final CalendarEventFeed eventFeed = gCalendarConnectService.getEventsFeed(calendar.getCalendarId());

                tran.begin();
                try {
                    calendar.setName(eventFeed.getTitle().getPlainText());
                    tran.commit();
                } catch (Exception e) {
                    log.log(Level.WARNING, "Cannot set calendar title", e);
                    tran.rollback();
                }

                log.info("Parsing calendar " + calendar.getCalendarId() + " (" + calendar.getName() + ")");
                final List<Group> groups = gCalParser.parseSchedule(eventFeed, now);
                for (Group group : groups) {
                    group.setCalendarKey(calendar.getEncodedKey());
                }
                allGroups.addAll(groups);
            }

            log.info("Storing new groups");

            tran.begin();

            pm.deletePersistentAll(schedule.getGroups());
            schedule.getGroups().clear();
            schedule.getGroups().addAll(allGroups);

            schedule.setLoadedFromGCal(new Date());

            tran.commit();
        } catch (Exception e) {
            log.log(Level.SEVERE, "", e);
            throw e;
        } finally {
            if (tran.isActive()) tran.rollback();
            //pm.close();
        }
    }


    public void publishToGCalendar(String scheduleKey) throws Exception {
        final PersistenceManager pm = PMF.getThreadLocalPersistenceManager();
        Schedule schedule = null;
        String error = null;
        try {
            schedule = pm.getObjectById(Schedule.class, scheduleKey);

            clearSyncQueue(pm, schedule.getEncodedKey());

            for (GCalendar gCalendar : schedule.getCalendars()) {
                GoogleCalendarSyncCalendar googleCalendarSyncCalendar = new GoogleCalendarSyncCalendar();
                googleCalendarSyncCalendar.setScheduleKey(scheduleKey);
                googleCalendarSyncCalendar.setGCalendarKey(gCalendar.getEncodedKey());
                pm.makePersistent(googleCalendarSyncCalendar);
            }
            enqueCalendarSync(scheduleKey);
        } catch (Throwable e) {
            log.log(Level.SEVERE, "", e);
            error = e.toString();
            if (schedule != null) clearSyncQueue(pm, schedule.getEncodedKey());
        } finally {
            storeSyncResult(schedule, error, error == null ? SyncStatus.IN_PROGRESS : SyncStatus.FINISHED);
        }
    }

    public static void clearSyncQueue(PersistenceManager pm, final String scheduleKey) {
        log.info("Clearing sync queue");
        Query query = pm.newQuery(GoogleCalendarSyncEvent.class, "scheduleKey == :s");
        try {
            Collection res = (Collection) query.execute(scheduleKey);
            pm.deletePersistentAll(res);
            query.closeAll();

            query = pm.newQuery(GoogleCalendarSyncCalendar.class, "scheduleKey == :s");
            res = (Collection) query.execute(scheduleKey);
            pm.deletePersistentAll(res);
        } finally {
            query.closeAll();
        }
    }

    public static void storeSyncResult(Schedule persistentSchedule, String error, SyncStatus syncStatus) {
        if (persistentSchedule == null) throw new RuntimeException("Schedule is null");

        final PersistenceManager pm = PMF.getThreadLocalPersistenceManager();
        final Transaction tran = pm.currentTransaction();
        tran.begin();
        try {
            GoogleCalendarSync googleCalendarSync = persistentSchedule.getGoogleCalendarSync();
            googleCalendarSync.setSyncStatus(syncStatus);
            googleCalendarSync.setLastPublishDate(new Date());
            if (syncStatus == SyncStatus.FINISHED) {
                if (error != null && error.length() > 500) error = error.substring(0, 500);
                googleCalendarSync.setLastPublishErrors(error);
            } else if (syncStatus == SyncStatus.IN_PROGRESS) {
                googleCalendarSync.setLastPublishErrors(null);
            }

            tran.commit();
        } finally {
            if (tran.isActive()) tran.rollback();
            //pm.close();
        }

    }


    public static void enqueEventsSync(String scheduleKey) throws TaskAlreadyExistsException {
        log.info("Creating gcalendar-sync-events-task for schedule " + scheduleKey);
        final Queue queue = QueueFactory.getQueue("gcalendar-sync");
        queue.add(withUrl("/tasks/gcalendar-sync-events-task").param("schedule", scheduleKey));
    }

    public static void enqueCalendarSync(String scheduleKey) throws TaskAlreadyExistsException {
        log.info("Creating gcalendar-sync-calendar-task for schedule " + scheduleKey);
        final Queue queue = QueueFactory.getQueue("gcalendar-sync");
        queue.add(withUrl("/tasks/gcalendar-sync-calendar-task").param("schedule", scheduleKey));
    }

    private ArrayList<GoogleCalendarSyncEvent> events = new ArrayList<GoogleCalendarSyncEvent>();

    public void updateGroupSchedules(PersistenceManager pm, Group group, String newCalendarKey, boolean ignoreHide) {
        this.updateGroupSchedules(pm, group, newCalendarKey, Operation.UPDATE, ignoreHide);
    }

    public void createGroupSchedules(PersistenceManager pm, Group group, String newCalendarKey) {
        this.updateGroupSchedules(pm, group, newCalendarKey, Operation.CREATE, false);
    }

    private void updateGroupSchedules(PersistenceManager pm, Group group, String newCalendarKey,
                                      final Operation operation, boolean ignoreHide) {
        final GCalendar gCalendar;
        if (newCalendarKey != null) {
            gCalendar = pm.getObjectById(GCalendar.class, newCalendarKey);
        } else {
            gCalendar = group.getCalendar(pm);
        }
        if (gCalendar != null && (ignoreHide || !group.isHidden())) {
            events.add(new GoogleCalendarSyncEvent(
                    group, gCalendar.getCalendarId(), operation));
        }
    }

    public void removeGroupSchedules(PersistenceManager pm, Group group) {
        final GCalendar gCalendar = group.getCalendar(pm);
        if (gCalendar != null) {
            for (ScheduleItem scheduleItem : group.getScheduleItems()) {
                if (scheduleItem.getGoogleEventId() != null) {
                    removeEvent(gCalendar.getCalendarId(), scheduleItem.getGoogleEventId(), scheduleItem);
                }
            }
        }
    }

    public void removeEvent(String calGoogleId, String eventGoogleId, ScheduleItem scheduleItem) {
        GoogleCalendarSyncEvent deleteEvent = new GoogleCalendarSyncEvent(eventGoogleId, calGoogleId, scheduleItem);
        events.add(deleteEvent);
    }

    public void updateScheduleItem(PersistenceManager pm, ScheduleItem scheduleItem, Group group) {
        final GCalendar gCalendar = group.getCalendar(pm);
        log.fine("GCalendar for scheduleItem = " + gCalendar);
        if (gCalendar != null && !group.isHidden()) {
            events.add(new GoogleCalendarSyncEvent(
                    scheduleItem, gCalendar.getCalendarId(), Operation.UPDATE));
        }
    }

    public void removeScheduleItem(PersistenceManager pm, ScheduleItem scheduleItem, Group group) {
        if (scheduleItem.getGoogleEventId() != null) {
            final GCalendar gCalendar = group.getCalendar(pm);
            log.fine("GCalendar for scheduleItem = " + gCalendar);
            if (gCalendar != null) {
                removeEvent(gCalendar.getCalendarId(), scheduleItem.getGoogleEventId(), scheduleItem);
            }
        }
    }

    public void sendEvents(String scheduleKey) {
        sendEvents(scheduleKey, false);
    }
    
    public void sendEvents(String scheduleKey, boolean enqueTask) {
        if (events.size() > 0) {
            final PersistenceManager pm = PMF.getThreadLocalPersistenceManager();
            final Transaction tran = pm.currentTransaction();
            final Query query = pm.newQuery(GoogleCalendarSync.class, "schedule == :s");
            try {
                Collection res = (Collection) query.execute(scheduleKey);
                final Iterator iterator = res.iterator();
                GoogleCalendarSync googleCalendarSync = iterator.hasNext() ? (GoogleCalendarSync) iterator.next() : null;

                if (googleCalendarSync == null || googleCalendarSync.getSessionToken() == null) {
                    log.info("Skipping sync as gcalendar autorization is not set");
                    events.clear();
                } else {

                    final long time = System.currentTimeMillis();
                    int num = 0;
                    for (GoogleCalendarSyncEvent event : events) {
                        log.fine("Adding sync event: " + event);
                        event.setTime(time);
                        event.setNum(num++);
                        event.setScheduleKey(scheduleKey);

                        tran.begin();
                        pm.makePersistent(event);
                        tran.commit();
                        //getEventsSyncQueue(scheduleKey).offer(event);
                    }
                    events.clear();

                    if (enqueTask)
                        enqueEventsSync(scheduleKey);
                }
            } finally {
                query.closeAll();
                if (tran.isActive()) tran.rollback();
            }
        }
    }


    /*
    public static ConcurrentLinkedQueue<String> getCalendarSyncQueue(String scheduleKey) {
        ConcurrentLinkedQueue<String> queue = scheduleCalendarSyncQueues.get(scheduleKey);
        if (queue == null) {
            scheduleCalendarSyncQueues.putIfAbsent(scheduleKey, new ConcurrentLinkedQueue<String>());
            queue = scheduleCalendarSyncQueues.get(scheduleKey); 
        }
        return queue;
    }
    */

    /*
    public static ConcurrentLinkedQueue<GoogleCalendarSyncEvent> getEventsSyncQueue(String scheduleKey) {
        ConcurrentLinkedQueue<GoogleCalendarSyncEvent> queue = scheduleEventQueues.get(scheduleKey);
        if (queue == null) {
            scheduleEventQueues.putIfAbsent(scheduleKey, new ConcurrentLinkedQueue<GoogleCalendarSyncEvent>());
            queue = scheduleEventQueues.get(scheduleKey);
        }
        return queue;
    }
    */
}
